/*
 *
 * Copyright Eclub organization.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.eclub.emotion.rootstone.serializer;

import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.log4j.Logger;

import com.eclub.emotion.rootstone.serializer.annotation.RsSerializable;
import com.eclub.emotion.rootstone.serializer.exception.ForbidSerializeException;
import com.eclub.emotion.rootstone.serializer.scanning.PackageNamesScanner;

/**
 * 
 * frequently-used data type Map
 * 
 * @author jun.yang, Wincor Nixdorf International GmbH
 * @version $Revision$
 */
public class CommonDataTypeMap {
	private static Logger LOGGER = Logger.getLogger(CommonDataTypeMap.class);

	private static Map<Class<?>, ClassItem> TypeIdMap = new HashMap<Class<?>, ClassItem>();
	private static Map<Integer, ClassItem> IdTypeMap = new HashMap<Integer, ClassItem>();
	
	private CommonDataTypeMap(){
		initialize();
	}
	
	public static void initialize() {
		TypeIdMap.clear();
		IdTypeMap.clear();
		ArrayList<ClassItem> ClassList = new ArrayList<ClassItem>();
		ClassList.add(new ClassItem(2, Object.class));
		ClassList.add(new ClassItem(3, Boolean.class, boolean.class));
		ClassList.add(new ClassItem(4, Character.class, char.class));
		ClassList.add(new ClassItem(5, Byte.class, byte.class));
		ClassList.add(new ClassItem(7, Short.class, short.class));
		ClassList.add(new ClassItem(9, Integer.class, int.class));
		ClassList.add(new ClassItem(11, Long.class, long.class));
		ClassList.add(new ClassItem(13, Float.class, float.class));
		ClassList.add(new ClassItem(14, Double.class, double.class));
		ClassList.add(new ClassItem(15, BigDecimal.class));
		ClassList.add(new ClassItem(16, Date.class, java.sql.Date.class, java.sql.Time.class, java.sql.Timestamp.class));
		ClassList.add(new ClassItem(18, String.class));
		ClassList.add(new ClassItem(19, List.class));
		ClassList.add(new ClassItem(23, Array.class));
		ClassList.add(new ClassItem(24, Map.class));
		for (ClassItem item : ClassList) {
			int id = item.getTypeId();
			Class<?>[] types = item.getTypes();
			for (Class<?> c : types) {
				TypeIdMap.put(c, item);
			}
			IdTypeMap.put(id, item);
		}

		String scanType = System.getProperty("gaea.serializer.scantype");
		if (scanType != null && scanType.equals("asyn")) {
			Thread th = new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println("Scan jar files begin!");
					try {
						LoadCustmeType();
					} catch (Exception ex) {
						ex.printStackTrace();
					}
					System.out.println("Scan jar files completed!");
				}
			});
			th.start();
		} else {
			System.out.println("Scan jar files begin!");
			try {
				LoadCustmeType();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			System.out.println("Scan jar files completed!");
		}
	}

	private static void LoadCustmeType() throws URISyntaxException, IOException, MalformedURLException, ClassNotFoundException {
		String basePackage = System.getProperty("gaea.serializer.basepakage");
		basePackage = basePackage == null ? "" : basePackage;
		PackageNamesScanner pkgScanner = new PackageNamesScanner(new String[] { basePackage });
		for (; pkgScanner.hasNext();) {
			String fileName = pkgScanner.next();
			LOGGER.info("scaning " + fileName);
			String className = fileName.replaceAll(".class", "").replaceAll("/", ".");
			Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
			RsSerializable annotation = clazz.getAnnotation(RsSerializable.class);
			if (annotation != null) {
				String name = annotation.name() != null ? clazz.getSimpleName() : annotation.name();
				int typeId = HashCodeBuilder.reflectionHashCode(name);
				TypeIdMap.put(clazz, new ClassItem(typeId, clazz));
				IdTypeMap.put(typeId, new ClassItem(typeId, clazz));
			}
		}
	}

	public static Class<?> getClass(int typeId) {
		if (typeId == 6) {
			typeId = 5;
		} else if (typeId == 20 || typeId == 21) {
			typeId = 19;
		} else if (typeId == 25) {
			typeId = 24;
		}
		ClassItem ci = IdTypeMap.get(typeId);
		if (ci != null) {
			return ci.getType();
		}
		return null;
	}

	public static int getTypeId(Class<?> type) throws ForbidSerializeException {
		int typeId = 0;
		if (type.isArray()) {
			type = Array.class;
		} else if (Map.class.isAssignableFrom(type)) {
			type = Map.class;
		} else if (List.class.isAssignableFrom(type)) {
			type = List.class;
		}
		ClassItem ci = TypeIdMap.get(type);
		if (ci != null) {
			typeId = ci.getTypeId();
		} else {
			RsSerializable ann = (RsSerializable) type.getAnnotation(RsSerializable.class);
			if (ann == null) {
				throw new ForbidSerializeException(type);
			}

			String name = StringUtils.isEmpty(ann.name()) ? ann.name() : type.getSimpleName();
			typeId = HashCodeBuilder.reflectionHashCode(name);
			setTypeMap(type, typeId);
		}
		return typeId;
	}

	public static void setTypeMap(Class<?> type, int typeId) {
		ClassItem ci = new ClassItem(typeId, type);
		TypeIdMap.put(type, ci);
		IdTypeMap.put(typeId, ci);
	}
}

class ClassItem {

	private Class<?>[] Types;
	private int TypeId;

	public ClassItem(int typeids, Class<?>... types) {
		Types = types;
		TypeId = typeids;
	}

	public Class<?> getType() {
		return Types[0];
	}

	public Class<?>[] getTypes() {
		return Types;
	}

	public int getTypeId() {
		return TypeId;
	}
}
